﻿using System;
using System.Collections;
using System.Drawing;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace JelleDruyts.Windows.Test
{
    [TestClass]
    public class ImageManipulatorTest
    {
        #region Constants

        public const string TestImagesPath = "Images";
        private const string LandscapeInputFileName = @"Images\Landscape.jpg";
        private const string PortraitInputFileName = @"Images\Portrait.jpg";
        private const string OutputFileName = "Output.jpg";

        #endregion

        #region Initialize & Cleanup

        [TestInitialize]
        public void TestInit()
        {
            Assert.IsFalse(File.Exists(OutputFileName));
        }

        [TestCleanup]
        public void TestCleanup()
        {
            File.Delete(OutputFileName);
        }

        #endregion

        #region Width & Height

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToDimensionsShouldNotResizeWithNullDimensions()
        {
            var fileName = LandscapeInputFileName;
            var image = ImageManipulator.ResizeToDimensions(fileName, null, null);
            image.CopyTo(OutputFileName);
            Assert.AreEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(ImageManipulator.GetDimensions(fileName), ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToDimensionsShouldNotResizeWithSameDimensions()
        {
            var fileName = LandscapeInputFileName;
            var originalSize = ImageManipulator.GetDimensions(fileName);
            var image = ImageManipulator.ResizeToDimensions(fileName, originalSize.Width, originalSize.Height);
            image.CopyTo(OutputFileName);
            Assert.AreEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(originalSize, ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToDimensionsShouldResizeToSpecifiedDimensionsWidthHeight()
        {
            var fileName = LandscapeInputFileName;
            var image = ImageManipulator.ResizeToDimensions(fileName, 100, 100);
            image.CopyTo(OutputFileName);
            Assert.AreNotEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(new Size(100, 100), ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToDimensionsShouldResizeToSpecifiedDimensionsWidth()
        {
            var fileName = LandscapeInputFileName;
            var image = ImageManipulator.ResizeToDimensions(fileName, 100, null);
            image.CopyTo(OutputFileName);
            Assert.AreNotEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            var originalSize = ImageManipulator.GetDimensions(fileName);
            Assert.AreEqual<Size>(new Size(100, (int)(originalSize.Height * 100 / (double)originalSize.Width)), ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToDimensionsShouldResizeToSpecifiedDimensionsHeight()
        {
            var fileName = LandscapeInputFileName;
            var image = ImageManipulator.ResizeToDimensions(fileName, null, 100);
            image.CopyTo(OutputFileName);
            Assert.AreNotEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            var originalSize = ImageManipulator.GetDimensions(fileName);
            Assert.AreEqual<Size>(new Size((int)(originalSize.Width * 100 / (double)originalSize.Height), 100), ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        #endregion

        #region Percentage

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToPercentageShouldNotResizeWithSamePercentage()
        {
            var fileName = LandscapeInputFileName;
            var originalSize = ImageManipulator.GetDimensions(fileName);
            var image = ImageManipulator.ResizeToPercentage(fileName, 1.0);
            image.CopyTo(OutputFileName);
            Assert.AreEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(originalSize, ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToPercentageShouldResizeToSpecifiedPercentage()
        {
            var fileName = LandscapeInputFileName;
            var percentage = 0.5;
            var originalSize = ImageManipulator.GetDimensions(fileName);
            var image = ImageManipulator.ResizeToPercentage(fileName, percentage);
            image.CopyTo(OutputFileName);
            Assert.AreNotEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(new Size((int)(percentage * originalSize.Width), (int)(percentage * originalSize.Height)), ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        #endregion

        #region Longest Side

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToLongestSideShouldNotResizeWithSameLongestSide()
        {
            var fileName = LandscapeInputFileName;
            var originalSize = ImageManipulator.GetDimensions(fileName);
            var image = ImageManipulator.ResizeToLongestSide(fileName, Math.Max(originalSize.Width, originalSize.Height));
            image.CopyTo(OutputFileName);
            Assert.AreEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(originalSize, ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToLongestSideShouldResizeToSpecifiedLongestSideLandscape()
        {
            var fileName = LandscapeInputFileName;
            var originalSize = ImageManipulator.GetDimensions(fileName);
            var image = ImageManipulator.ResizeToLongestSide(fileName, 100);
            image.CopyTo(OutputFileName);
            Assert.AreNotEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(new Size(100, (int)(originalSize.Height * 100 / (double)originalSize.Width)), ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToLongestSideShouldResizeToSpecifiedLongestSidePortrait()
        {
            var fileName = PortraitInputFileName;
            var originalSize = ImageManipulator.GetDimensions(fileName);
            var image = ImageManipulator.ResizeToLongestSide(fileName, 100);
            image.CopyTo(OutputFileName);
            Assert.AreNotEqual<long>(new FileInfo(fileName).Length, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(new Size((int)(originalSize.Width * 100 / (double)originalSize.Height), 100), ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        #endregion

        #region Maximum Size

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToMaximumSizeShouldNotResizeIfSmallerThanMaximumSize()
        {
            var fileName = LandscapeInputFileName;
            var originalFileSize = new FileInfo(fileName).Length;
            var image = ImageManipulator.ResizeToMaximumSize(fileName, originalFileSize + 1);
            image.CopyTo(OutputFileName);
            Assert.AreEqual<long>(originalFileSize, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(ImageManipulator.GetDimensions(fileName), ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToMaximumSizeShouldNotResizeIfEqualToMaximumSize()
        {
            var fileName = LandscapeInputFileName;
            var originalFileSize = new FileInfo(fileName).Length;
            var image = ImageManipulator.ResizeToMaximumSize(fileName, originalFileSize);
            image.CopyTo(OutputFileName);
            Assert.AreEqual<long>(originalFileSize, new FileInfo(OutputFileName).Length);
            Assert.AreEqual<Size>(ImageManipulator.GetDimensions(fileName), ImageManipulator.GetDimensions(OutputFileName));
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToMaximumSizeShouldResizeToMaximumSize()
        {
            var fileName = PortraitInputFileName;
            var percentage = 0.5;
            var originalFileSize = new FileInfo(fileName).Length;
            var maximumFileSize = (long)(originalFileSize * percentage);
            var image = ImageManipulator.ResizeToMaximumSize(fileName, maximumFileSize);
            image.CopyTo(OutputFileName);
            Assert.IsTrue(new FileInfo(OutputFileName).Length <= maximumFileSize);
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        public void ResizeToMaximumSizeShouldResizeEvenIfNearlySameSize()
        {
            var fileName = LandscapeInputFileName;
            var originalFileSize = new FileInfo(fileName).Length;
            var maximumFileSize = originalFileSize - 1;
            var image = ImageManipulator.ResizeToMaximumSize(fileName, maximumFileSize);
            image.CopyTo(OutputFileName);
            Assert.IsTrue(new FileInfo(OutputFileName).Length <= maximumFileSize);
            VerifyMetadata(fileName, OutputFileName);
        }

        [TestMethod]
        [DeploymentItem(TestImagesPath, TestImagesPath)]
        [ExpectedException(typeof(ArgumentException))]
        public void ResizeToMaximumSizeShouldNotResizeToImpossibleSize()
        {
            var fileName = LandscapeInputFileName;
            var originalFileSize = new FileInfo(fileName).Length;
            var maximumFileSize = 1;
            var image = ImageManipulator.ResizeToMaximumSize(fileName, maximumFileSize);
        }

        #endregion

        #region Helper Methods

        private static void VerifyMetadata(string expectedImageFilename, string actualImageFilename)
        {
            var expectedMetadata = ImageManipulator.GetMetadata(expectedImageFilename);
            var actualMetadata = ImageManipulator.GetMetadata(actualImageFilename);
            Assert.IsNotNull(actualMetadata);
            Assert.AreEqual(expectedMetadata.ApplicationName, actualMetadata.ApplicationName);
            VerifyCollection(expectedMetadata.Author, actualMetadata.Author);
            Assert.AreEqual(expectedMetadata.CameraManufacturer, actualMetadata.CameraManufacturer);
            Assert.AreEqual(expectedMetadata.CameraModel, actualMetadata.CameraModel);
            Assert.AreEqual(expectedMetadata.Comment, actualMetadata.Comment);
            Assert.AreEqual(expectedMetadata.Copyright, actualMetadata.Copyright);
            Assert.AreEqual(expectedMetadata.DateTaken, actualMetadata.DateTaken);
            Assert.AreEqual(expectedMetadata.Format, actualMetadata.Format);
            VerifyCollection(expectedMetadata.Keywords, actualMetadata.Keywords);
            Assert.AreEqual(expectedMetadata.Location, actualMetadata.Location);
            Assert.AreEqual(expectedMetadata.Rating, actualMetadata.Rating);
            Assert.AreEqual(expectedMetadata.Subject, actualMetadata.Subject);
            Assert.AreEqual(expectedMetadata.Title, actualMetadata.Title);
        }

        private static void VerifyCollection(IList expected, IList actual)
        {
            if (expected == null)
            {
                Assert.IsNull(actual);
            }
            else
            {
                Assert.AreEqual(expected.Count, actual.Count);
                for (var i = 0; i < actual.Count; i++)
                {
                    Assert.AreEqual(actual[i], expected[i]);
                }
            }
        }

        #endregion
    }
}